package com.craftworks.ui;

import cn.hutool.extra.qrcode.QrCodeUtil;
import com.craftworks.UrlSniffer;
import com.craftworks.rtmp.RtmpFilter;
import com.craftworks.rtmp.RtmpFilterListener;
import com.craftworks.util.ComponentUtil;
import com.craftworks.util.OSUtils;
import com.github.kokorin.jaffree.LogLevel;
import com.github.kokorin.jaffree.ffmpeg.FFmpeg;
import com.github.kokorin.jaffree.ffmpeg.FFmpegResultFuture;
import com.github.kokorin.jaffree.ffmpeg.UrlInput;
import com.github.kokorin.jaffree.ffmpeg.UrlOutput;
import lombok.extern.slf4j.Slf4j;
import org.jdesktop.application.Action;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.Task;
import oshi.hardware.NetworkIF;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;

@Slf4j
public class MainViewController implements RtmpFilterListener
{
    private final FrameView frameView;
    private final ActionMap actionMap;
    private JComboBox<NetworkIF> comboNetworkInterface;
    private JButton btnStartStop;
    private JButton btnClearList;
    private JLabel labelRecordingTimer;
    private JButton btnStopRecording;
    private JLabel qrcodeLabel;
    private JList<String> urlList;
    private JButton btnRecord;
    private JButton btnPlay;
    private JPanel urlOpsPanel;
    private String selectedUrl;
    private DefaultListModel<String> urlListModel;
    private FFmpegResultFuture recordingTask;

    public MainViewController(FrameView view)
    {
        frameView = view;
        actionMap = view.getContext().getActionMap(this);
        initUI();
    }

    private void initUI()
    {
        createAndSetupMenuBar();
        createAndSetupToolBar();
        createAndSetupWorkspace();
        setupInitialStates();
    }

    private void createAndSetupMenuBar()
    {
        JMenu menuSys = new JMenu("操作");
        menuSys.add(createMenuItem("exitApp", "退出", "退出 " + UrlSniffer.APP_NAME));

        JMenu menuHelp = new JMenu("帮助");
        menuHelp.add(createMenuItem("showRecordedVideos", "查看视频", "查看已录制的视频文件"));
        menuHelp.add(createMenuItem("showAbout", "关于", "关于 " + UrlSniffer.APP_NAME));

        JMenuBar menuBar = new JMenuBar();
        menuBar.add(menuSys);
        menuBar.add(menuHelp);
        frameView.setMenuBar(menuBar);
    }

    private JMenuItem createMenuItem(String action, String text, String tooltip)
    {
        JMenuItem item = new JMenuItem();
        item.setAction(actionMap.get(action));
        item.setText(text);
        item.setToolTipText(tooltip);
        return item;
    }

    private JButton createButton(String action, String text, String tooltip)
    {
        JButton button = new JButton();
        button.setAction(actionMap.get(action));
        button.setText(text);
        button.setToolTipText(tooltip);
        return button;
    }

    private void createAndSetupToolBar()
    {
        JLabel label = new JLabel("选择工作网卡：");
        comboNetworkInterface = new JComboBox<>(OSUtils.getUsableNetworkIFs());
        comboNetworkInterface.setRenderer(new NetworkIFListRenderer());
        comboNetworkInterface.setMaximumSize(comboNetworkInterface.getPreferredSize());
        btnStartStop = createButton("startStopFiltering", "开始监听", "监听网卡流量");
        btnClearList = createButton("clearUrlList", "清空地址", "清空地址列表");
        btnStopRecording = createButton("stopRecording", "停止", "停止录流");

        labelRecordingTimer = new JLabel();
        labelRecordingTimer.setFont(new Font("MS YaHei", Font.BOLD, 24));
        labelRecordingTimer.setBorder(BorderFactory.createLineBorder(Color.RED, 3));

        JToolBar toolBar = new JToolBar();
        toolBar.setMargin(new Insets(5, 5, 5, 5));
        toolBar.add(label);
        toolBar.add(comboNetworkInterface);
        toolBar.add(btnStartStop);
        toolBar.addSeparator();
        toolBar.add(btnClearList);
        toolBar.add(Box.createHorizontalStrut(60));
        toolBar.add(labelRecordingTimer);
        toolBar.add(btnStopRecording);
        toolBar.add(Box.createHorizontalGlue());

        frameView.setToolBar(toolBar);
    }

    private void createAndSetupWorkspace()
    {
        JPanel panel = new JPanel(new BorderLayout());

        urlListModel = new DefaultListModel<>();
        urlList = new JList<>();
        urlList.setModel(urlListModel);
        urlList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        urlList.setDragEnabled(false);
        urlList.getSelectionModel().addListSelectionListener(this::onUrlSelected);
        JScrollPane scrollPane = new JScrollPane(urlList);
        panel.add(scrollPane, BorderLayout.CENTER);

        qrcodeLabel = new JLabel();
        qrcodeLabel.setAlignmentX(0.5f);
        btnPlay = createButton("openInPlayer", "播放", "在播放器中打开视频");
        btnPlay.setAlignmentX(0.5f);
        btnRecord = createButton("recordToFile", "录制", "录制视频到文件");
        btnRecord.setAlignmentX(0.5f);
        urlOpsPanel = new JPanel();
        urlOpsPanel.setLayout(new BoxLayout(urlOpsPanel, BoxLayout.PAGE_AXIS));
        urlOpsPanel.add(qrcodeLabel);
        urlOpsPanel.add(Box.createVerticalStrut(5));
        urlOpsPanel.add(btnPlay);
        urlOpsPanel.add(btnRecord);
        urlOpsPanel.setVisible(false);
        panel.add(urlOpsPanel, BorderLayout.EAST);

        frameView.getRootPane().getContentPane().add(panel, BorderLayout.CENTER);
    }

    private void setupInitialStates()
    {
        labelRecordingTimer.setVisible(false);
        btnStopRecording.setVisible(false);
        frameView.getFrame().setTitle(UrlSniffer.APP_NAME + " " + UrlSniffer.APP_VERSION);
        ComponentUtil.setPreferSizeAndLocateToCenter(frameView.getFrame(), 0.75, 0.75);
    }

    @Action
    public void exitApp()
    {
        frameView.getApplication().exit();
    }

    @Action
    public void showRecordedVideos()
    {
        Path recordsDir = Paths.get("records");

        try
        {
            Desktop.getDesktop().open(recordsDir.toFile());
        } catch (IOException ex)
        {
            log.warn("打开视频目录异常：{}", ex.getMessage());
        }
    }

    @Action
    public void showAbout()
    {
        try
        {
            JDialog dialog = new JDialog(frameView.getFrame());

            BufferedImage image = ImageIO.read(Objects.requireNonNull(getClass().getResourceAsStream("/images/wx-zanshang.png")));
            JLabel imageLabel = new JLabel(new ImageIcon(image));
            imageLabel.setAlignmentX(0.5f);
            JLabel textLabel = new JLabel("老板，赏瓶快乐肥宅水啦");
            textLabel.setAlignmentX(0.5f);
            textLabel.setFont(new Font("KaiTi", Font.BOLD, 28));
            JLabel smileLabel = new JLabel("(●'◡'●)");
            smileLabel.setAlignmentX(0.5f);
            smileLabel.setFont(new Font("MS YaHei", Font.BOLD, 24));

            JButton okButton = new JButton("确定");
            okButton.addActionListener(e -> dialog.setVisible(false));
            okButton.setAlignmentX(0.5f);

            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
            panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
            panel.add(imageLabel);
            panel.add(Box.createVerticalStrut(10));
            panel.add(textLabel);
            panel.add(smileLabel);
            panel.add(Box.createVerticalStrut(30));
            panel.add(okButton);

            dialog.setContentPane(panel);
            dialog.setModal(true);
            dialog.setMinimumSize(new Dimension(400, dialog.getHeight()));
            dialog.setMaximumSize(new Dimension(400, dialog.getHeight()));
            dialog.setResizable(false);
            dialog.pack();
            dialog.setTitle(String.format("关于 %s", UrlSniffer.APP_NAME));
            dialog.setLocationRelativeTo(frameView.getFrame());
            dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            dialog.getRootPane().setDefaultButton(okButton);
            dialog.getRootPane().registerKeyboardAction(e -> dialog.setVisible(false),
                                                        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                                                        JComponent.WHEN_IN_FOCUSED_WINDOW);

            dialog.setVisible(true);
            dialog.dispose();
        } catch (Exception ex)
        {
            JOptionPane.showMessageDialog(frameView.getFrame(),
                                          String.format("%s %s%n©2022 %s", UrlSniffer.APP_NAME, UrlSniffer.APP_VERSION, UrlSniffer.APP_VENDOR),
                                          String.format("关于 %s", UrlSniffer.APP_NAME),
                                          JOptionPane.INFORMATION_MESSAGE);
        }
    }

    @Action
    public void clearUrlList()
    {
        urlListModel.clear();
        urlOpsPanel.setVisible(false);
    }

    @Action
    public void startStopFiltering()
    {
        btnStartStop.setEnabled(false);
        RtmpFilter filter = RtmpFilter.getInstance();
        if (!filter.isStarted())
        {
            NetworkIF selected = (NetworkIF) comboNetworkInterface.getSelectedItem();
            if (selected == null)
            {
                btnStartStop.setEnabled(true);
                log.info("未选择网卡");
                return;
            }

            filter.setNetworkInterface(selected.queryNetworkInterface());
            filter.setListener(this);
            filter.start();
        } else
        {
            filter.stop();
        }
    }

    @Action
    public void openInPlayer()
    {
        try
        {
            Runtime.getRuntime().exec("./MPC-HC/mpc-hc64.exe " + selectedUrl);
        } catch (IOException ex)
        {
            log.warn("播放视频时发生异常：{}", ex.getMessage());
        }
    }

    @Action
    public void recordToFile()
    {
        if (recordingTask != null)
        {
            JOptionPane.showMessageDialog(frameView.getFrame(), "正在录流，停止后才能录下一个。");
            return;
        }

        try
        {
            Path outputDir = Paths.get("records");
            Files.createDirectories(outputDir);
            Path outputFile = outputDir.resolve(String.format("%d.mp4", System.currentTimeMillis()));

            recordingTask = FFmpeg.atPath(Paths.get("ffmpeg"))
                                  .addInput(UrlInput.fromUrl(selectedUrl).addArgument("-re"))
                                  .addOutput(UrlOutput.toPath(outputFile).addArguments("-c", "copy"))
                                  .setOverwriteOutput(true)
                                  .setLogLevel(LogLevel.QUIET)
                                  .setProgressListener(progress ->
                                                       {
                                                           long seconds = progress.getTime(TimeUnit.SECONDS);
                                                           String duration = formatDuration(seconds);
                                                           SwingUtilities.invokeLater(() -> labelRecordingTimer.setText(" " + duration + " "));
                                                       })
                                  .executeAsync();

            recordingTask.toCompletableFuture()
                         .thenRun(() ->
                                  {
                                      FFmpegResultFuture task = recordingTask;
                                      if (task != null)
                                          task.graceStop();
                                      recordingTask = null;
                                      SwingUtilities.invokeLater(() ->
                                                                 {
                                                                     labelRecordingTimer.setText(" 00:00:00 ");
                                                                     btnStopRecording.setEnabled(false);
                                                                     labelRecordingTimer.setVisible(false);
                                                                     btnStopRecording.setVisible(false);
                                                                 });
                                  });

            labelRecordingTimer.setText(" 00:00:00 ");
            btnStopRecording.setEnabled(true);
            labelRecordingTimer.setVisible(true);
            btnStopRecording.setVisible(true);
        } catch (IOException ex)
        {
            log.warn("I/O异常：{}", ex.getMessage());
            JOptionPane.showMessageDialog(frameView.getFrame(),
                                          "无法启动录流程序", "错误",
                                          JOptionPane.WARNING_MESSAGE);
        }
    }

    @Action
    public void stopRecording()
    {
        if (recordingTask == null)
            return;

        btnStopRecording.setEnabled(false);
        Task<Void, Void> stopWorker = new Task<Void, Void>(frameView.getApplication())
        {
            @Override
            protected Void doInBackground() throws Exception
            {
                FFmpegResultFuture task = recordingTask;
                if (task != null)
                {
                    task.graceStop();
                    task.get(10, TimeUnit.SECONDS);
                    if (!task.isDone())
                        task.forceStop();
                }
                return null;
            }

            @Override
            protected void succeeded(Void result)
            {
                labelRecordingTimer.setText(null);
                labelRecordingTimer.setVisible(false);
                btnStopRecording.setEnabled(true);
                btnStopRecording.setVisible(false);
                recordingTask = null;
            }

            @Override
            protected void interrupted(InterruptedException e)
            {
                FFmpegResultFuture task = recordingTask;
                if (task != null)
                    task.forceStop();
                labelRecordingTimer.setText(null);
                labelRecordingTimer.setVisible(false);
                btnStopRecording.setEnabled(true);
                btnStopRecording.setVisible(false);
                recordingTask = null;
            }

            @Override
            protected void failed(Throwable cause)
            {
                JOptionPane.showMessageDialog(frameView.getFrame(),
                                              "无法停止录流过程，请重启程序。", "警告",
                                              JOptionPane.WARNING_MESSAGE);
                labelRecordingTimer.setText(null);
                labelRecordingTimer.setVisible(false);
                btnStopRecording.setEnabled(true);
                btnStopRecording.setVisible(false);
                recordingTask = null;
            }
        };
        stopWorker.execute();
    }

    public void stopRecordingTask()
    {
        FFmpegResultFuture task = recordingTask;
        if (task != null)
            task.graceStop();
    }

    public void stopFiltering()
    {
        RtmpFilter.getInstance().stop();
    }

    private void onUrlSelected(ListSelectionEvent listSelectionEvent)
    {
        String url = urlList.getSelectedValue();
        if (url == null)
        {
            urlOpsPanel.setVisible(false);
        } else
        {
            selectedUrl = url;
            createQRCodeFromURL(url);
            urlOpsPanel.setVisible(true);
        }
    }

    private void createQRCodeFromURL(String url)
    {
        int width = 160;  // 二维码宽度
        int height = 160; // 二维码高度

        try
        {
            BufferedImage qrcode = QrCodeUtil.generate(url, width, height);
            qrcodeLabel.setIcon(new ImageIcon(qrcode));
        } catch (Exception e)
        {
            log.error("无法生成二维码：{}", e.getMessage());
        }
    }

    @Override
    public void filterStarted()
    {
        SwingUtilities.invokeLater(() ->
                                   {
                                       btnStartStop.setText("停止监听");
                                       btnStartStop.setEnabled(true);
                                   });
    }

    @Override
    public void filterStopped()
    {
        SwingUtilities.invokeLater(() ->
                                   {
                                       btnStartStop.setText("开始监听");
                                       btnStartStop.setEnabled(true);
                                   });
    }

    @Override
    public void urlFiltered(String url)
    {
        SwingUtilities.invokeLater(() ->
                                   {
                                       urlListModel.add(urlListModel.getSize(), url);
                                       urlList.setSelectedValue(url, true);
                                       btnStartStop.setEnabled(true);
                                       actionMap.get("openInPlayer").actionPerformed(null);
                                   });
    }

    @Override
    public void exceptionCaught(Exception ex)
    {
        log.warn("捕获异常：{}", ex.getMessage());
    }

    private String formatDuration(long seconds)
    {
        int hh = (int) (seconds / 3600);
        int mm = (int) (seconds % 3600) / 60;
        int ss = (int) (seconds % 60);

        return (hh < 100)
               ? String.format("%02d:%02d:%02d", hh, mm, ss)
               : String.format("%d:%02d:%02d", hh, mm, ss);
    }
}
